package time_windown

import (
	"fmt"
	"sync"
	"time"
)

// 时间范围 + 存档记录空降 + 边界锚点
type RateLimiterV2 struct {
	WindowSize time.Duration
	// v1中这里存档了时间——准确来说是单个实体的时间，v2为了面向多个值，存档的是值+时间的组合
	CountsMap map[string][]time.Time // key：对值的唯一标识。 value: 记录下的时间
	maxCount  int                    // 统一控制所有实体记录的频率
	clean     int

	stopCh chan struct{} // 关闭信号通道

	maxMapCap int // map中key记录的最大数量，超过数量则需要清理

	mu sync.RWMutex
}

func (r *RateLimiterV2) Allow(key string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	/* 通过当前时间now推动窗口前进，窗口外的数据失效，只关注窗口内的数据来判断频率 */

	now := time.Now()

	// 判断记录与时间的情况，需要维护存档数据
	// 判断记录与时间，去除key对应的过期的时间记录
	// 如： map["k1"] = [t1 , t2 , t3] ， t1~3 是 k1 出现的三个时间点
	// 因为 t1 t2 t3 都是按照时间顺序append，所以是有序的，如果 t2 过期，则表示 t2 之前的 t1 也是过期的
	// 应该直接收缩数据为 map["k1"] = [ t3 ]

	lastExpireIndex := -1 // 距离最近的无效的时间的下标
	for i, ti := range r.CountsMap[key] {
		if ti.Add(r.WindowSize).Before(now) {
			//r.CountsMap[key] = r.CountsMap[key][i+1:]
			lastExpireIndex = i
			// fmt.Println("expire", ti.String(), now.String())
		} else {
			fmt.Println("break", ti.String(), now.String())
			break
		}
	}
	if lastExpireIndex != -1 {
		r.CountsMap[key] = r.CountsMap[key][lastExpireIndex+1:]
	}

	if len(r.CountsMap[key]) >= r.maxCount {
		return false
	}

	r.CountsMap[key] = append(r.CountsMap[key], now)

	// recordList, ok := r.CountsMap[key]
	// if !ok {
	// 	r.CountsMap[key] = append(r.CountsMap[key], now)
	// } else {
	// 	for _, ti := range r.CountsMap[key] {
	// 	}
	// }

	return true
}

func (r *RateLimiterV2) Stop() {
	close(r.stopCh)
}

func (r *RateLimiterV2) SetMapCap(n int) {
	r.maxMapCap = n
}

func (r *RateLimiterV2) Reset() {
	r.mu.Lock()
	defer r.mu.Unlock()
	if len(r.CountsMap) > 0 {
		r.CountsMap = make(map[string][]time.Time)
	}
}

// 清理过期数据防止内存泄漏
func (r *RateLimiterV2) Clean() {

	// 超过了最大数量才需要清理
	if len(r.CountsMap) <= r.maxMapCap {
		return
	}

	r.clean++ // 记录清扫次数
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now()

	// 判断记录时间
	for k, v := range r.CountsMap {
		if ind := len(v) - 1; ind >= 0 {
			// 如果最后一次记录时间已经在窗口外，则可以直接删除记录
			if v[ind].Add(r.WindowSize).Before(now) {
				delete(r.CountsMap, k)
				// 如果map的value为指针，则delete会释放指针对应的内存（但是不会释放map本身的桶）
			}
		}
	}

	// 如果清扫过了50次，重新替换map，让旧map中无效的桶被垃圾回收
	if r.clean > 50 {
		newMap := make(map[string][]time.Time, len(r.CountsMap))
		for k, v := range r.CountsMap {
			newMap[k] = v
		}
		r.CountsMap = newMap
		r.clean = 0
	}
}

func NewRateLimiterV2(windowSize time.Duration, maxCount int) *RateLimiterV2 {
	res := &RateLimiterV2{
		WindowSize: windowSize,
		maxCount:   maxCount,
		CountsMap:  make(map[string][]time.Time),
		stopCh:     make(chan struct{}),
		maxMapCap:  100,
	}

	// 启动清理协程，防止内存泄漏
	go func() {
		// 用反比例函数 y= 15/x + 2 控制运行的频率
		// 如果窗口小于1分钟，则控制 17 个窗口时间执行一次
		// 如果窗口等于5分钟，则控制 5 个窗口时间执行一次
		// 如果窗口大于30分钟，则使用 2 个窗口执行一次

		s := windowSize.Minutes() + 1
		num := 2
		if s < 30 {
			num = 15/int(s) + 2
		}

		if windowSize <= 0 {
			windowSize = 1 * time.Second
		}

		ticker := time.NewTicker(windowSize * time.Duration(num))
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				res.Clean()
			case <-res.stopCh:
				return
			}
		}
	}()

	return res
}
